/**
 * This is the main test file.
 * The test should be compiled this one of the flag:
 * 				USE_WF_QUEUE, USE_LOCK_QUEUE, USE_HP_QUEUE
 * Each flag defines the queue that whould be tested.
 */
#include <time.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <pthread.h>
#include <glib.h>
#include <time.h>

#ifdef USE_WF_QUEUE
#include "WFQueue.h"
#define QUEUE WFQueue
#define PrintQueueType printf("\tUsing WFQueue, ");
#endif
#ifdef USE_optWF_QUEUE
#include "optWFQueue.h"
#define QUEUE optWFQueue
#define PrintQueueType printf("\tUsing optWFQueue, ");
#endif
#ifdef USE_LOCK_QUEUE
#include "LockQueue.h"
#define QUEUE LockQueue
#define PrintQueueType printf("\tUsing LockQueue, ");
#endif
#ifdef USE_HP_QUEUE
#include "HPQueue.h"
#define QUEUE HPQueue
#define PrintQueueType printf("\tUsing HPQueue, ");
#endif

#ifdef USE_HP
#define PrintHP printf("Using HP\n");
#else
#define PrintHP printf("Not Using HP\n");
#endif

#ifdef DEBUG
int casArr[20] = {0};
#endif

/**
 * run data
 */
int ITER, NUM_OF_THREADS;
#define NUM_OF_REPEATS 4

/**
 * Parameters for the thread
 */
typedef struct Data_t {
	QUEUE *queue;
	int tid;
	int* res;
	float totalTime;
} Data;

/**
 * Thread main function
 */
void* thread_func(void *ptr) {
	Data *data = (Data*)ptr;
	data->totalTime = 0.0;
	GTimer* timer = g_timer_new();
	g_timer_start(timer);

	//run main function
	int val;
	int tid = data->tid;
	QUEUE *queue = data->queue;
	for (int i=0; i<ITER; i++) {
		enq(queue, tid, tid*ITER + i);
		val = deq(queue, tid);
		if (val == -1){
			printf("0) ERROR in thread %d! empty value in iter %d\n\t", tid, i);
		}
		else if (val>=0 && val<NUM_OF_THREADS*ITER){
			if (data->res[NUM_BYTES_IN_CACHE_LINE*val] == -1){
				data->res[NUM_BYTES_IN_CACHE_LINE*val] = tid;
			}
			else{
				printf(" 1) I am %d. but %d already got this node\n", tid,
						data->res[NUM_BYTES_IN_CACHE_LINE*val]);
			}
		}
		else{
			printf(" 2) I am %d. I got %d\n", tid, val);
		}
	}
	g_timer_stop(timer);
	data->totalTime = g_timer_elapsed(timer, NULL);
	g_timer_destroy(timer);

	return 0;
}



/**
 * Main
 */
int main(int argc, char *argv[]) {
	if (argc != 3){
		printf("usage: ./queue.exe NUM_OF_THREADS ITER\n");
		exit(-1);
	}

	NUM_OF_THREADS = atoi(argv[1]);
	ITER = atoi(argv[2]);

	printf("\n*********************************************\n");
	printf("\tNUM_OF_THREADS=%d, ITER=%d\n", NUM_OF_THREADS, ITER);
	PrintQueueType; PrintHP;

	/**********************************************************************/
	Data tData[NUM_OF_THREADS];
	int* res = (int*)malloc(NUM_BYTES_IN_CACHE_LINE *
			ITER * (1+NUM_OF_THREADS)*sizeof(int));
	float totalTime = 0;

#ifdef DEBUG
	float totalOfCas = 0;
#endif

	for (int iter = 0; iter<NUM_OF_REPEATS; iter++){

		memset(res, -1, NUM_BYTES_IN_CACHE_LINE * ITER * (1+NUM_OF_THREADS)*sizeof(int));

		QUEUE* queue = getQueue(NUM_OF_THREADS);

		pthread_t threads1[NUM_OF_THREADS];

		for (int i=0; i<NUM_OF_THREADS; i++){
			tData[i].queue = queue;
			tData[i].tid = i;
			tData[i].res = res;
			pthread_create(&threads1[i], NULL, thread_func, (void*)&tData[i]);
		}
		float maxTime = 0;
		for (int i=0; i<NUM_OF_THREADS; i++) {
			pthread_join(threads1[i], NULL);
			maxTime = maxTime > tData[i].totalTime ? maxTime : tData[i].totalTime;
		}
		totalTime += maxTime;

		for (int i=0; i<NUM_OF_THREADS; i++){
			for (int j=0; j<ITER; j++){
				if (res[NUM_BYTES_IN_CACHE_LINE*(i*ITER+j)]==-1)
					printf("3) error in test. res[%d]=-1\n",
							NUM_BYTES_IN_CACHE_LINE*(i*ITER+j));
			}
		}
#ifdef DEBUG
		float sumOfCas = 0;
		for (int i=0; i<NUM_OF_THREADS; i++){
			sumOfCas+= casArr[i];
		}
		sumOfCas = sumOfCas / NUM_OF_THREADS;
		totalOfCas += sumOfCas;
#endif

		deleteQueue(queue);

	}
	free(res);

	totalTime  = totalTime / NUM_OF_REPEATS;

	printf("\tFinished in %f \n", totalTime);
#ifdef DEBUG
	printf("\ttotalOfCas = %d \n", totalOfCas);
#endif
	printf("*********************************************\n");
	int resVal = (int)(1000000.0*totalTime);
	return resVal;
}

